package cn.aitrox.ry.service.image.bean;

import cn.aitrox.ry.bean.DicomFileAttributes;
import cn.aitrox.ry.bean.RespResult;
import cn.aitrox.ry.common.exception.CustomBizException;
import cn.aitrox.ry.constant.CommonConstant;
import cn.aitrox.ry.enumtype.RespCodeEnum;
import cn.aitrox.ry.service.dwh.dto.DwhImageDto;
import cn.aitrox.ry.service.dwh.dto.DwhSeriesDto;
import cn.aitrox.ry.service.dwh.dto.in.DwhImageQueryInDto;
import cn.aitrox.ry.service.dwh.dto.in.DwhSeriesQueryInDto;
import cn.aitrox.ry.service.dwh.feign.DwhImageFeign;
import cn.aitrox.ry.service.dwh.feign.DwhSeriesFeign;
import cn.aitrox.ry.service.image.dao.ImageDao;
import cn.aitrox.ry.service.image.dao.ImageInfoDao;
import cn.aitrox.ry.service.image.dto.video.VideoInfnDto;
import cn.aitrox.ry.service.image.entity.ImageInfoMongoEntity;
import cn.aitrox.ry.service.image.entity.ImageMongoEntity;
import cn.aitrox.ry.service.image.entity.ImageTransferItemEntity;
import cn.aitrox.ry.service.image.enumtype.ImageFileTypeEnum;
import cn.aitrox.ry.service.image.enumtype.ImageGroupTypeEnum;
import cn.aitrox.ry.service.oss.feign.OssFeign;
import cn.aitrox.ry.util.ArrayUtil;
import cn.aitrox.ry.util.DateUtil;
import cn.aitrox.ry.util.DicomUtils;
import cn.aitrox.ry.util.FileDownLoadUtil;
import cn.aitrox.ry.util.FsUtil;
import cn.aitrox.ry.util.JSONUtil;
import cn.aitrox.ry.util.LogUtil;
import cn.aitrox.ry.util.ZipUtil;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.dcm4che3.imageio.plugins.dcm.DicomImageReader;
import org.dcm4che3.imageio.plugins.dcm.DicomImageReaderSpi;
import org.dcm4che3.io.DicomInputStream;
import org.dcm4che3.util.SafeClose;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Component
public class VideoFrameExtractTransferHandler extends ImageTransferHandler {

    private static final Logger LOG = LoggerFactory.getLogger(VideoFrameExtractTransferHandler.class);

    private static final String JPG_FORMAT_FILE_NAME = "jpg";
    private static final String JPG_FILE_SUFFIX = ".jpg";
    private static final String ZIP_FILE_NAME = "segment.zip";

    private static final String FILE_TYPE_JPG = "jpg";
    private static final String FILE_TYPE_ZIP = "zip";

    @Autowired
    @Lazy
    private DwhImageFeign dwhImageFeign;

    @Autowired
    @Lazy
    private DwhSeriesFeign dwhSeriesFeign;

    @Autowired
    @Lazy
    private OssFeign ossFeign;

    @Autowired
    private ImageDao imageDao;

    @Autowired
    private ImageInfoDao imageInfoDao;


    @Override
    public RespResult handler(ImageTransferItemEntity itemEntity) {
        RespResult<List<DwhSeriesDto>> seriesQueryResult = dwhSeriesFeign.seriesQuery(new DwhSeriesQueryInDto(Arrays.asList(itemEntity.getSeriesUid())));
        if (!seriesQueryResult._isOk()) {
            return RespResult.secByError(RespCodeEnum.ERROR.getCode(), "查询数仓序列失败");
        }
        if (ArrayUtil.isEmpty(seriesQueryResult.getData())) {
            return RespResult.secByError(RespCodeEnum.NOT_EXIST.getCode(), "序列不存在");
        }
        DwhSeriesDto dwhSeriesDto = seriesQueryResult.getData().get(0);

        // 查询数仓获取下载地址
        RespResult<Map<String, List<DwhImageDto>>> imageQueryResult = dwhImageFeign.imageQuery(new DwhImageQueryInDto(Arrays.asList(itemEntity.getSeriesUid())));
        if (!imageQueryResult._isOk()) {
            return RespResult.secByError(RespCodeEnum.ERROR.getCode(), "查询数仓影像失败");
        }
        if (ArrayUtil.isEmpty(imageQueryResult.getData())) {
            return RespResult.secByError(RespCodeEnum.NOT_EXIST.getCode(), "序列影像不存在");
        }
        List<DwhImageDto> dwhImageDtoList = imageQueryResult.getData().get(itemEntity.getSeriesUid());
        if (ArrayUtil.isEmpty(dwhImageDtoList)) {
            return RespResult.secByError(RespCodeEnum.NOT_EXIST.getCode(), "序列影像不存在");
        }

        // 下载影像文件
        File tmpParentFile = new File(getTmpShareParentPath("image-transfer"));
        File tmpJpgParentFile = new File(tmpParentFile, "JPG");
        File tmpZipParentFile = new File(tmpParentFile, "ZIP");
        File tmpZipFile = new File(tmpZipParentFile, ZIP_FILE_NAME);
        try {
            if (!tmpJpgParentFile.exists()) {
                tmpJpgParentFile.mkdirs();
            }
            if (!tmpZipParentFile.exists()) {
                tmpZipParentFile.mkdirs();
            }
            // 下载源文件
            String fileNameSuffix = FsUtil.getFileNameSuffix(dwhImageDtoList.get(0).getPath());
            File srcFile = new File(tmpParentFile, fileNameSuffix);
            FileDownLoadUtil.downLoad(dwhImageDtoList.get(0).getUrlLan(), srcFile.getAbsolutePath(), 1000, 300000);

            // 转换 DCM或者视频
            VideoInfnDto videoInfnDto;
            if ("DCM".equalsIgnoreCase(dwhSeriesDto.getFileType())) {
                videoInfnDto = this.transferByDcm(srcFile, tmpJpgParentFile);
            } else {
                videoInfnDto = this.transferByVideo(srcFile, tmpJpgParentFile);
            }

            String videoInfoJson = JSONUtil.toJson(videoInfnDto, VideoInfnDto.class);

            // JPG压缩成ZIP
            try {
                ZipUtil.zipPath(tmpJpgParentFile.getAbsolutePath(), tmpZipFile);
            } catch (Exception e) {
                LOG.error(LogUtil.genException(e, "视频分割文件压缩失败", "series", itemEntity.getSeriesUid()), e);
                return RespResult.secByError(RespCodeEnum.ERROR.getCode(), "视频分割文件压缩失败");
            }

            Date date = new Date();

            // 上传JPG、ZIP到OSS
            List<ImageMongoEntity> imageMongoEntityList = new ArrayList<>();
            for (File jpgFile : tmpJpgParentFile.listFiles()) {
                Integer number = parseNumByNumberFileName(jpgFile.getName());

                String jpgOssKey = this.genImageOssPath(itemEntity.getSeriesUid(), date, FILE_TYPE_JPG, jpgFile.getName());
                ossFeign.putLocal(jpgOssKey, jpgFile.getAbsolutePath(), imageNacosProp.getOssType());

                ImageMongoEntity imageMongoEntity = new ImageMongoEntity(null, itemEntity.getSeriesUid(), ImageGroupTypeEnum.SERIES_IMAGE_TRANSFER.getGroupType(), number, jpgOssKey, imageNacosProp.getOssType(), ImageFileTypeEnum.JPG.getFileType(), itemEntity.getMode(), null, date, date);
                imageMongoEntityList.add(imageMongoEntity);
            }
            String zipOssKey = this.genImageOssPath(itemEntity.getSeriesUid(), date, FILE_TYPE_ZIP, ZIP_FILE_NAME);
            ossFeign.putLocal(zipOssKey, tmpZipFile.getAbsolutePath(), imageNacosProp.getOssType());

            ImageMongoEntity imageMongoEntity = new ImageMongoEntity(null, itemEntity.getSeriesUid(), ImageGroupTypeEnum.SERIES_IMAGE_TRANSFER.getGroupType(), 1, zipOssKey, imageNacosProp.getOssType(), ImageFileTypeEnum.ZIP.getFileType(), itemEntity.getMode(), null, date, date);
            imageMongoEntityList.add(imageMongoEntity);

            ImageInfoMongoEntity imageInfoMongoEntity = new ImageInfoMongoEntity(null, itemEntity.getSeriesUid(), ImageGroupTypeEnum.SERIES_IMAGE_TRANSFER.getGroupType(), itemEntity.getMode(), videoInfoJson, date, date);

            // 保存Mongo，先删除再保存
            imageDao.deleteByGroupGroupTypeMode(itemEntity.getSeriesUid(), ImageGroupTypeEnum.SERIES_IMAGE_TRANSFER.getGroupType(), itemEntity.getMode());
            imageInfoDao.deleteByGroupGroupTypeMode(itemEntity.getSeriesUid(), ImageGroupTypeEnum.SERIES_IMAGE_TRANSFER.getGroupType(), itemEntity.getMode());

            imageDao.batchSave(imageMongoEntityList);
            imageInfoDao.save(imageInfoMongoEntity);

            return RespResult.ok();
        } catch (Exception e) {
            LOG.error(LogUtil.genException(e, "VideoFrameExtract handler fail", "series", itemEntity.getSeriesUid()), e);
            return RespResult.secByError(RespCodeEnum.ERROR.getCode(), "转换失败: " + e.getMessage());
        } finally {
            if (tmpParentFile.exists()) {
                FsUtil.deleteDir(tmpParentFile);
            }
        }
    }

    private VideoInfnDto transferByVideo(File srcFile, File jpgParentFile) throws IOException {
        VideoInfnDto videoInfnDto;
        // 读取视频信息，进行帧分割
        FFmpegFrameGrabber ff = null;
        try {
            ff = FFmpegFrameGrabber.createDefault(srcFile);
            LOG.info(LogUtil.gen("FFmpeg start....", "srcFile", srcFile.getAbsolutePath()));
            ff.start();

            Long lengthInTime = ff.getLengthInTime();
            Double videoFrameRate = ff.getVideoFrameRate();
            Integer lengthInVideoFrames = ff.getLengthInVideoFrames();
            Integer imageWidth = ff.getImageWidth();
            Integer imageHeight = ff.getImageHeight();
            videoInfnDto = new VideoInfnDto(lengthInVideoFrames, videoFrameRate.floatValue(), lengthInTime, imageWidth, imageHeight, null);

            int index = 1;
            while (true) {
                // 获取每一帧的数据
                Frame frame = ff.grabImage();
                if (frame == null) {
                    break;
                }
                Java2DFrameConverter converter = new Java2DFrameConverter();
                // 写出图片
                BufferedImage bi = converter.getBufferedImage(frame);
                String outFileName = getNumberFileName(index) + JPG_FILE_SUFFIX;
                File outFile = new File(jpgParentFile, outFileName);
                ImageIO.write(bi, JPG_FORMAT_FILE_NAME, outFile);

                index++;
            }
            LOG.info(LogUtil.gen("FFmpeg process end", "srcFile", srcFile.getAbsolutePath()));
            return videoInfnDto;
        } finally {
            if (null != ff) {
                try {
                    ff.stop();
                } catch (FrameGrabber.Exception e) {
                    LOG.error(LogUtil.gen("FFmpegFrameGrabber stop err: " + e.toString()));
                }
            }
        }
    }

    private VideoInfnDto transferByDcm(File srcFile, File jpgParentFile) throws IOException {
        DicomFileAttributes da = DicomUtils.loadDcmFileOnlyTag(srcFile);
        if (null == da) {
            throw new CustomBizException("DCM解析异常");
        }

        // 每帧的间隔时间
        Float frameTime = da.getFrameTime();
        // 帧率
        Float frameRate = null;
        Integer numberOfFrame = null == da.getNumberOfFrames() ? 1 : da.getNumberOfFrames();

        if (null != da.getRecommendedDisplayFrameRate()) {
            // 默认使用推荐帧率
            frameRate = da.getRecommendedDisplayFrameRate().floatValue();
        }
        if (null == frameRate) {
            // 如果没有推荐帧率，计算帧率：1秒/每帧间隔时间
            if (null != frameTime) {
                frameRate = ((Integer) Math.round(1000 / frameTime)).floatValue();
            } else {
                // 使用默认帧率
                frameRate = imageNacosProp.getDefaultVideoRate();
            }

        }
        VideoInfnDto videoInfnDto = new VideoInfnDto(numberOfFrame, frameRate, null, Integer.parseInt(da.getColumns()), Integer.parseInt(da.getRows()), da.getFrameTime());
        DicomInputStream dcmIS = null;
        try {
            dcmIS = new DicomInputStream(srcFile);
            DicomImageReader dicomImageReader = new DicomImageReader(new DicomImageReaderSpi());
            dicomImageReader.setInput(dcmIS);
            ImageReadParam defaultReadParam = dicomImageReader.getDefaultReadParam();

            for (int i = 0; i < numberOfFrame; i++) {
                String outFileName = getNumberFileName(i + 1) + JPG_FILE_SUFFIX;
                File outFile = new File(jpgParentFile, outFileName);
                BufferedImage bufferedImage = dicomImageReader.read(i, defaultReadParam);
                ImageIO.write(bufferedImage, JPG_FORMAT_FILE_NAME, outFile);
            }

            return videoInfnDto;
        } finally {
            SafeClose.close(dcmIS);
        }
    }

    private static String getNumberFileName(Integer number) {
        String numStr = number + "";
        if (numStr.length() != 6) {
            int zeroNum = 6 - numStr.length();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < zeroNum; i++) {
                sb.append(0);
            }
            return sb.toString() + numStr;
        }
        return numStr;
    }

    private Integer parseNumByNumberFileName(String numberFileName) {
        int index = numberFileName.indexOf(CommonConstant.FILE_NAME_SEPARATOR);
        String numberName;
        if (index != -1) {
            numberName = numberFileName.substring(0, index);
        } else {
            numberName = numberFileName;
        }
        for (int i = 0; i < numberName.length(); i++) {
            char ch = numberFileName.charAt(i);
            if (ch != 0) {
                return Integer.parseInt(numberName.substring(i));
            }
        }
        return 0;
    }


    // anno/yyyy/MM/dd/image/series/jpg/000001.jpg
    private String genImageOssPath(String seriesUid, Date date, String fileType, String fileName) {
        String datePath = DateUtil.formatDate(date, "yyyy/MM/dd");
        return StringUtils.joinWith(CommonConstant.DEFAULT_FILE_SEPARATOR, "anno", datePath, "image", seriesUid, fileType, fileName);
    }
}
